<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Hephaestus AI Assistant</title>
  <style>
    html,
    body,
    #app {
      height: 100%;
      overflow: hidden;
      font-family: SF Pro SC, SF Pro Text, SF Pro Icons, PingFang SC, Helvetica Neue, Helvetica, Arial, sans-serif;
      font-size: 1rem;
    }

    body {
      padding: 0;
      color: #1d1d1f;
    }

    .vscode-light .prompt {
      background-color: #fff;
    }

    @keyframes fade-in-up {
      from {
        opacity: 0;
        transform: translateY(8px);
      }

      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    .animate-fade-in {
      animation: fade-in-up 0.3s ease-out forwards;
    }

    .animate-bounce {
      animation: bounce 1.4s infinite ease-in-out;
    }

    .delay-100 {
      animation-delay: 0.1s;
    }

    .delay-200 {
      animation-delay: 0.2s;
    }

    .typewriter-container {
      display: inline-block;
      max-width: 100%;
    }

    @keyframes blink {

      from,
      to {
        opacity: 1;
      }

      50% {
        opacity: 0;
      }
    }

    button:disabled {
      opacity: 0.4;
      cursor: not-allowed;
    }
  </style>
  <style>
    .markdown-body {
      padding: 0.5rem;
      background-color: inherit !important;
    }

    .markdown-body pre {
      width: 100%;
      border-radius: 0.375rem;
    }


    /* .markdown-body {
      font-family: inherit;
      font-size: inherit;
      line-height: inherit;
      color: inherit;
    }

    .markdown-body>* {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    

    .markdown-body code {
      font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
      font-size: 0.9em;
    }  */
  </style>
  <link rel="stylesheet" href="${webviewUri}/libs/markdown-it/github-markdown.min.css">
  <link rel="stylesheet" href="${webviewUri}/libs/highlight/atom-one-dark.min.css">
  <script src="${webviewUri}/libs/tailwindcss/tailwindcss-3.4.14.min.js"></script>
  <script src="${webviewUri}/libs/vue/vue.global.min.js"></script>
  <script src="${webviewUri}/libs/axios/axios.min.js"></script>
  <script src="${webviewUri}/libs/markdown-it/markdown-it.min.js"></script>
  <script src="${webviewUri}/libs/highlight/highlight.min.js"></script>
</head>

<body class="dark:bg-[#303133]">
  <figure id="app" class="flex flex-col  h-100 max-w-3xl mx-auto">
    <!-- <div id="test"></div> -->
    <header class="flex items-center justify-end">
      <label class="inline-flex items-center cursor-pointer">
        <input type="checkbox" v-model="switchState" class="sr-only peer">
        <div
          class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600">
        </div>
        <span class="ms-3 text-sm font-medium text-gray-900">{{switchLabel}}</span>
      </label>
    </header>
    <main class="flex-1 overflow-y-auto box-border p-4">
      <section v-for="item in results" :key="item.id">
        <div class="flex gap-3 animate-fade-in group justify-end ">
          <div class="my-2">
            <div class="bg-gray-100 p-2 rounded-md shadow-sm transition-all duration-200 ease-out prompt">
              <p class="text-sm leading-6 whitespace-pre-wrap break-words max-w-sm">
                {{item.prompt}}
              </p>
            </div>
          </div>
        </div>
        <div class="flex gap-3 animate-fade-in">
          <svg v-if="item.loading" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
            <circle cx="12" cy="2" r="0" fill="currentColor">
              <animate attributeName="r" begin="0" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(45 12 12)">
              <animate attributeName="r" begin="0.125s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(90 12 12)">
              <animate attributeName="r" begin="0.25s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(135 12 12)">
              <animate attributeName="r" begin="0.375s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(180 12 12)">
              <animate attributeName="r" begin="0.5s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(225 12 12)">
              <animate attributeName="r" begin="0.625s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(270 12 12)">
              <animate attributeName="r" begin="0.75s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
            <circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(315 12 12)">
              <animate attributeName="r" begin="0.875s" calcMode="spline" dur="1s"
                keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite"
                values="0;2;0;0" />
            </circle>
          </svg>
          <div class="my-2 break-all w-full">
            <type-writer v-if="item?.data?.content?.text" :text="item?.data?.content?.text" :speed="speed"
              :cursor="true"></type-writer>
          </div>
        </div>
      </section>

    </main>
    <footer class="p-4">
      <div class="border border-gray-300 rounded-lg p-2 bg-white relative">
        <textarea v-model="prompt" placeholder="发消息..."
          class="w-full border-none text-sm pl-2 resize-none overflow-hidden focus:outline-none" rows="1"
          style="min-height: 24px; max-height: 200px"></textarea>
        <div class="flex items-center justify-end space-x-2">
          <button class="text-gray-600 hover:text-gray-900 w-5 h-5" title="附件">
            <svg class="w-full h-full" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
              <path fill="currentColor"
                d="m28.1 18.9l-15-15c-2.5-2.6-6.6-2.6-9.2-.1s-2.6 6.7 0 9.2l.1.1L6.8 16l1.4-1.4l-2.9-2.9c-1.7-1.7-1.7-4.6 0-6.3s4.6-1.8 6.3-.1l.1.1l14.9 14.9c1.8 1.7 1.8 4.6.1 6.3c-1.7 1.8-4.6 1.8-6.3.1l-.1-.1l-7.4-7.4c-1-1-.9-2.6 0-3.5c1-.9 2.5-.9 3.5 0l4.1 4.1l1.4-1.4l-4.2-4.2c-1.8-1.7-4.6-1.6-6.3.2c-1.6 1.7-1.6 4.4 0 6.2l7.5 7.5c2.5 2.6 6.6 2.6 9.2.1s2.6-6.7 0-9.3c0 .1 0 0 0 0" />
            </svg>
          </button>
          <button class="text-gray-600 hover:text-gray-900 w-5 h-5" title="图片">
            <svg class="w-full h-full" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
              <path fill="currentColor" d="M19 14a3 3 0 1 0-3-3a3 3 0 0 0 3 3m0-4a1 1 0 1 1-1 1a1 1 0 0 1 1-1" />
              <path fill="currentColor"
                d="M26 4H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 22H6v-6l5-5l5.59 5.59a2 2 0 0 0 2.82 0L21 19l5 5Zm0-4.83l-3.59-3.59a2 2 0 0 0-2.82 0L18 19.17l-5.59-5.59a2 2 0 0 0-2.82 0L6 17.17V6h20Z" />
            </svg>
          </button>
          <button
            class="bg-gradient-to-br from-blue-400 to-blue-600 text-white rounded-full w-8 h-8 flex items-center justify-center"
            title="发送" @click="addResult" :disabled="loading">
            <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
              <path fill="currentColor" d="M16 4L6 14l1.41 1.41L15 7.83V28h2V7.83l7.59 7.58L26 14z" />
            </svg>
          </button>
        </div>
      </div>
    </footer>
  </figure>·

  <script>
    //tailwindcss
    tailwind.config = {
      darkMode: 'class',
      theme: {
        extend: {
          colors: {
            clifford: "#da373d",
            primary: {
              light: '#ffffff',
              dark: '#1a1a1a'
            },
            secondary: {
              light: '#f3f4f6',
              dark: '#374151'
            }
          },
        },
      },
    };

    // vscode环境
    let vscode = null
    if (typeof acquireVsCodeApi !== 'undefined') {
      vscode = acquireVsCodeApi();
    }

    // 定义 TypeWriter 组件
    const TypeWriter = {
      props: {
        text: {
          type: String,
          required: true
        },
        speed: {
          type: Number,
          default: 50
        },
        cursor: {
          type: Boolean,
          default: true
        }
      },
      setup(props) {
        const { ref, onMounted, onUnmounted } = Vue;
        const displayText = ref('');
        const renderedText = ref('');
        const showCursor = ref(props.cursor);
        let animationFrameId;
        let lastTimestamp = 0;

        // 初始化markdown-it并配置highlight.js
        const md = new markdownit({
          html: true,
          linkify: true,
          typographer: true,
          highlight: function (str, lang) {
            try {
              if (lang && hljs.getLanguage(lang)) {
                return hljs.highlight(str, {
                  language: lang,
                  ignoreIllegals: true
                }).value;
              }
              return hljs.highlightAuto(str).value;
            } catch (__) {
              return ''; // 异常时返回原始代码
            }
          }
        });

        // 优化后的高亮逻辑
        const highlightCode = () => {
          Vue.nextTick(() => {
            document.querySelectorAll('pre code').forEach(block => {
              hljs.highlightElement(block);
            });
          });
        };

        const processText = (text) => {
          if (typeof text !== 'string') return '';
          return md.render(text.replace(/\r\n/g, '\n'));
        };

        const type = (timestamp) => {
          if (!lastTimestamp) lastTimestamp = timestamp;
          const elapsed = timestamp - lastTimestamp;

          if (elapsed >= props.speed) {
            if (displayText.value.length < props.text.length) {
              displayText.value = props.text.slice(0, displayText.value.length + 1);
              renderedText.value = processText(displayText.value);
              lastTimestamp = timestamp;
            } else {
              showCursor.value = false;
              highlightCode(); // 最终渲染完成后执行高亮
            }
          }

          if (displayText.value.length < props.text.length) {
            animationFrameId = requestAnimationFrame(type);
          }
        };

        onMounted(() => {
          animationFrameId = requestAnimationFrame(type);
          // 初始化时注册全局高亮器
          hljs.configure({
            ignoreUnescapedHTML: true,
            cssSelector: 'pre code'
          });
        });

        onUnmounted(() => {
          cancelAnimationFrame(animationFrameId);
        });

        return { renderedText, showCursor };
      },
      template: `
        <div class="typewriter-container markdown-body text-sm">
          <span v-html="renderedText"></span>
          <span v-if="showCursor" class="cursor"></span>
        </div>
      `
    };

    // 初始化vue3
    const { createApp, ref, computed } = Vue
    const app = createApp({
      components: {
        'type-writer': TypeWriter
      },
      setup() {
        const message = ref('Hello Vue!');
        const results = ref([]);
        const loading = ref(false)
        const prompt = ref('请使用python编写一个观察者模式的示例')
        const switchState = ref(false);
        const switchLabel = computed(() => switchState.value ? 'On' : 'Off');

        const addResult = async () => {
          try {
            if (loading.value) {
              return;
            }
            loading.value = true

            // 先放提示词然后再放结果
            const id = results.value.length;
            const pvalue = prompt.value;
            const promptHistory = id === 0 ? [] : results.value.map(item => {
              return {
                content: item.prompt,
                role: 'assistant'
              }
            });
            results.value.push({
              id,
              loading: loading.value,
              prompt: pvalue,
            });
            prompt.value = '';

            // 使用cdn方式发送请求
            const DEEPSEEK_API_URL = `http://140.210.32.46:7861/chat`;
            axios.post(`${DEEPSEEK_API_URL}/knowledge_base_chat`, {
              query: pvalue,
              history: promptHistory,
              model: 'glm4',
              stream: false,
              "mode": "local_kb",
              "kb_name": "kms_new",
              "level": "0",
              "top_k": 3,
              "score_threshold": 2,
              "temperature": 0.7,
              "max_tokens": 0,
              "prompt_name": "default",
              "return_direct": false
            }).then((response) => {
              const data = JSON.parse(response.data);
              const text = data.choices[0]['message']['content'];
              window.postMessage({
                command: 'showResponse',
                response: {
                  id: id,
                  content: { text },
                  response: data,
                }
              });
            });
          } catch (err) {
            console.error(err);
            loading.value = false;
          }
        };

        return {
          message,
          results,
          loading,
          prompt,
          addResult
        }
      }
    }).mount('#app');


    // 消息监听
    window.addEventListener("message", (event) => {
      const message = event.data;
      if (message.command === "showResponse") {
        const oResult = app.results.find((item) => item.id === message.response.id);
        oResult.data = { content: { text: message.response.content.text } };
        oResult.loading = false
        app.loading = false;

        // // debugging for develper 
        // document.getElementById('test').innerHTML += JSON.stringify(message)
        // // END debugging for develper 
        return
      }

      // error
      if (message.command === "error") {
        document.getElementById('test').innerHTML += JSON.stringify(message)
        return
      }
    });

    // 文本域高度自适应
    const textarea = document.querySelector("textarea");
    textarea.addEventListener("input", function () {
      this.style.height = "auto";
      this.style.height = this.scrollHeight + "px";
    });
  </script>
</body>

</html>